{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "官方文档： https://websockets.readthedocs.io/en/stable/intro.html#\n",
    "\n",
    "\n",
    "# Getting started(Version: 8.1)\n",
    "\n",
    "## 环境：python3.7及以上\n",
    "\n",
    "## 安装 \n",
    "\n",
    "> pip install websockets\n",
    "\n",
    "## 测试\n",
    "\n",
    ">python -m websockets wss://echo.websocket.org/\n",
    "\n",
    "发送什么就返回什么\n",
    "\n",
    "## 基本示例\n",
    "\n",
    "**服务端**：\n",
    "\n",
    "不断监听客户端连接，当客户端发送一个name字符串后，返回一条 'hello' + name 的问候消息。\n",
    "\n",
    "```py\n",
    "# WS server example\n",
    "\n",
    "import asyncio\n",
    "import websockets\n",
    "\n",
    "\n",
    "async def hello(websocket, path):\n",
    "    # path标识请求路径，可以来自定义需求\n",
    "    name = await websocket.recv()\n",
    "    print(f\"< {name}\")\n",
    "\n",
    "    greeting = f\"Hello {name}!\"\n",
    "\n",
    "    await websocket.send(greeting)\n",
    "    print(f\"> {greeting}\")\n",
    "\n",
    "\n",
    "start_server = websockets.serve(hello, \"localhost\", 8765)\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```\n",
    "\n",
    "**客户端**:\n",
    "\n",
    "客户端连接到server，执行协程处理器 handler，当协程退出后与server断开连接。\n",
    "\n",
    "```py\n",
    "# WS client example\n",
    "\n",
    "import asyncio\n",
    "import websockets\n",
    "\n",
    "async def hello():\n",
    "    uri = \"ws://localhost:8765\"\n",
    "    async with websockets.connect(uri) as websocket:\n",
    "        name = input(\"What's your name? \")\n",
    "\n",
    "        await websocket.send(name)\n",
    "        print(f\"> {name}\")\n",
    "\n",
    "        greeting = await websocket.recv()\n",
    "        print(f\"< {greeting}\")\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(hello())\n",
    "```\n",
    "\n",
    "connect（）通过 上下文管理器保证了协程退出之前关闭socket连接。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## SSL安全连接示例\n",
    "\n",
    "安全的WebSocket连接提高了保密性和可靠性，降低了了使用不安全的proxy代理服务的风险。\n",
    "\n",
    "WSS协议之于WS就像HTTPS之于HTTP: 连接是用传输层安全(TLS)加密的，TLS通常被称为安全套接字层(SSL)。WSS需要类似HTTPS的TLS证书。\n",
    "\n",
    "\n",
    "下面介绍如改写上面的服务器示例以提供安全连接。请参阅`ssl`模块的文档配置上下文。\n",
    "\n",
    "```py\n",
    "\n",
    "# WSS (WS over TLS) server example, with a self-signed certificate\n",
    "\n",
    "import asyncio\n",
    "import pathlib\n",
    "import ssl\n",
    "import websockets\n",
    "\n",
    "async def hello(websocket, path):\n",
    "    name = await websocket.recv()\n",
    "    print(f\"< {name}\")\n",
    "\n",
    "    greeting = f\"Hello {name}!\"\n",
    "\n",
    "    await websocket.send(greeting)\n",
    "    print(f\"> {greeting}\")\n",
    "\n",
    "ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n",
    "localhost_pem = pathlib.Path(__file__).with_name(\"localhost.pem\")\n",
    "ssl_context.load_cert_chain(localhost_pem)\n",
    "\n",
    "start_server = websockets.serve(\n",
    "    hello, \"localhost\", 8765, ssl=ssl_context\n",
    ")\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```\n",
    "\n",
    "改写相应的客户端安全连接实现：\n",
    "\n",
    "```py\n",
    "# WSS (WS over TLS) client example, with a self-signed certificate\n",
    "\n",
    "import asyncio\n",
    "import pathlib\n",
    "import ssl\n",
    "import websockets\n",
    "\n",
    "ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n",
    "localhost_pem = pathlib.Path(__file__).with_name(\"localhost.pem\")\n",
    "ssl_context.load_verify_locations(localhost_pem)\n",
    "\n",
    "async def hello():\n",
    "    uri = \"wss://localhost:8765\"\n",
    "    async with websockets.connect(\n",
    "        uri, ssl=ssl_context\n",
    "    ) as websocket:\n",
    "        name = input(\"What's your name? \")\n",
    "\n",
    "        await websocket.send(name)\n",
    "        print(f\"> {name}\")\n",
    "\n",
    "        greeting = await websocket.recv()\n",
    "        print(f\"< {greeting}\")\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(hello())\n",
    "```\n",
    "\n",
    "注意：\n",
    "\n",
    "此处客户端需要一个ssl上下文，因为此处使用的是一个 自签名的CA证书。\n",
    "\n",
    "如果使用有效的证书(即由Python安装信任的CA签名)连接到安全的WebSocket服务器的客户端可以简单地将ssl=True传递给connect()，而不必构建ssl上下文。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用浏览器连接到服务器\n",
    "\n",
    "从浏览器连接到我们自己实现的服务器：\n",
    "\n",
    "从console里运行以下程序：\n",
    "\n",
    "```py\n",
    "# WS server that sends messages at random intervals\n",
    "\n",
    "import asyncio\n",
    "import datetime\n",
    "import random\n",
    "import websockets\n",
    "\n",
    "async def time(websocket, path):\n",
    "    while True:\n",
    "        now = datetime.datetime.utcnow().isoformat() + \"Z\"\n",
    "        await websocket.send(now)\n",
    "        await asyncio.sleep(random.random() * 3)\n",
    "\n",
    "start_server = websockets.serve(time, \"127.0.0.1\", 5678)\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```\n",
    "\n",
    "将以下代码写入html文件，并从浏览器里打开\n",
    "\n",
    "```html\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "    <head>\n",
    "        <title>WebSocket demo</title>\n",
    "    </head>\n",
    "    <body>\n",
    "        <script>\n",
    "            var ws = new WebSocket(\"ws://127.0.0.1:5678/\"),\n",
    "                messages = document.createElement('ul');\n",
    "            ws.onmessage = function (event) {\n",
    "                var messages = document.getElementsByTagName('ul')[0],\n",
    "                    message = document.createElement('li'),\n",
    "                    content = document.createTextNode(event.data);\n",
    "                message.appendChild(content);\n",
    "                messages.appendChild(message);\n",
    "            };\n",
    "            document.body.appendChild(messages);\n",
    "        </script>\n",
    "    </body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 多客户端同步\n",
    "\n",
    "一个websocket服务器可以从多个客户端接收消息，处理消息并同步状态到所有客户端。\n",
    "\n",
    "以下示例展示了任意一客户端将一变量counter进行增加或减少后，所有已连接的客户端都能同步更新counter的实时值。\n",
    "\n",
    "协程模块 [`asyncio`](https://docs.python.org/3/library/asyncio.html#module-asyncio)保证了变量的更新是按先后顺序的。\n",
    "\n",
    "服务端代码：\n",
    "```py\n",
    "# WS server example that synchronizes state across clients\n",
    "\n",
    "import asyncio\n",
    "import json\n",
    "import logging\n",
    "import websockets\n",
    "\n",
    "logging.basicConfig()\n",
    "\n",
    "STATE = {\"value\": 0}\n",
    "\n",
    "# 保存所有在线客户端\n",
    "USERS = set()\n",
    "\n",
    "\n",
    "def state_event():\n",
    "    return json.dumps({\"type\": \"state\", **STATE})\n",
    "\n",
    "\n",
    "def users_event():\n",
    "    return json.dumps({\"type\": \"users\", \"count\": len(USERS)})\n",
    "\n",
    "\n",
    "# 更新所有客户端显示的counter值\n",
    "async def notify_state():\n",
    "    if USERS:  # asyncio.wait doesn't accept an empty list\n",
    "        message = state_event()\n",
    "        await asyncio.wait([user.send(message) for user in USERS])\n",
    "\n",
    "\n",
    "# 通知客户端在线数量\n",
    "async def notify_users():\n",
    "    if USERS:  # asyncio.wait doesn't accept an empty list\n",
    "        message = users_event()\n",
    "        await asyncio.wait([user.send(message) for user in USERS])\n",
    "\n",
    "\n",
    "# 注册客户端\n",
    "async def register(websocket):\n",
    "    USERS.add(websocket)\n",
    "    await notify_users()\n",
    "\n",
    "# 注销客户端\n",
    "async def unregister(websocket):\n",
    "    USERS.remove(websocket)\n",
    "    await notify_users()\n",
    "\n",
    "\n",
    "async def counter(websocket, path):\n",
    "    # register(websocket) sends user_event() to websocket\n",
    "    await register(websocket)\n",
    "    try:\n",
    "        await websocket.send(state_event())\n",
    "        # 迭代websocket以不断接收消息，此处要求对象实现了 __iter__()、__await__()、 __aenter__()、 __aexit__() 方法。\n",
    "        async for message in websocket:\n",
    "            data = json.loads(message)\n",
    "            if data[\"action\"] == \"minus\":\n",
    "                STATE[\"value\"] -= 1\n",
    "                await notify_state()\n",
    "            elif data[\"action\"] == \"plus\":\n",
    "                STATE[\"value\"] += 1\n",
    "                await notify_state()\n",
    "            else:\n",
    "                logging.error(\"unsupported event: {}\", data)\n",
    "    finally:\n",
    "    # 客户端断开后，退出上面的 for 循环，即客户端协程退出后\n",
    "        await unregister(websocket)\n",
    "\n",
    "\n",
    "start_server = websockets.serve(counter, \"localhost\", 6789)\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```\n",
    "\n",
    "将以下代码写入html文件，并用浏览器打开多个页面模拟多个ws客户端\n",
    "\n",
    "```html\n",
    "<html>\n",
    "    <head>\n",
    "        <title>WebSocket demo</title>\n",
    "        <style type=\"text/css\">\n",
    "            body {\n",
    "                font-family: \"Courier New\", sans-serif;\n",
    "                text-align: center;\n",
    "            }\n",
    "            .buttons {\n",
    "                font-size: 4em;\n",
    "                display: flex;\n",
    "                justify-content: center;\n",
    "            }\n",
    "            .button, .value {\n",
    "                line-height: 1;\n",
    "                padding: 2rem;\n",
    "                margin: 2rem;\n",
    "                border: medium solid;\n",
    "                min-height: 1em;\n",
    "                min-width: 1em;\n",
    "            }\n",
    "            .button {\n",
    "                cursor: pointer;\n",
    "                user-select: none;\n",
    "            }\n",
    "            .minus {\n",
    "                color: red;\n",
    "            }\n",
    "            .plus {\n",
    "                color: green;\n",
    "            }\n",
    "            .value {\n",
    "                min-width: 2em;\n",
    "            }\n",
    "            .state {\n",
    "                font-size: 2em;\n",
    "            }\n",
    "        </style>\n",
    "    </head>\n",
    "    <body>\n",
    "        <div class=\"buttons\">\n",
    "            <div class=\"minus button\">-</div>\n",
    "            <div class=\"value\">?</div>\n",
    "            <div class=\"plus button\">+</div>\n",
    "        </div>\n",
    "        <div class=\"state\">\n",
    "            <span class=\"users\">?</span> online\n",
    "        </div>\n",
    "        <script>\n",
    "            var minus = document.querySelector('.minus'),\n",
    "                plus = document.querySelector('.plus'),\n",
    "                value = document.querySelector('.value'),\n",
    "                users = document.querySelector('.users'),\n",
    "                websocket = new WebSocket(\"ws://127.0.0.1:6789/\");\n",
    "            minus.onclick = function (event) {\n",
    "                websocket.send(JSON.stringify({action: 'minus'}));\n",
    "            }\n",
    "            plus.onclick = function (event) {\n",
    "                websocket.send(JSON.stringify({action: 'plus'}));\n",
    "            }\n",
    "            websocket.onmessage = function (event) {\n",
    "                data = JSON.parse(event.data);\n",
    "                switch (data.type) {\n",
    "                    case 'state':\n",
    "                        value.textContent = data.value;\n",
    "                        break;\n",
    "                    case 'users':\n",
    "                        users.textContent = (\n",
    "                            data.count.toString() + \" user\" +\n",
    "                            (data.count == 1 ? \"\" : \"s\"));\n",
    "                        break;\n",
    "                    default:\n",
    "                        console.error(\n",
    "                            \"unsupported event\", data);\n",
    "                }\n",
    "            };\n",
    "        </script>\n",
    "    </body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Server常用模板\n",
    "\n",
    "你要在连接的生命周期里处理多条消息，你必须实现一个loop，以下为你提供了一个构建Websocket Server的基础模板。\n",
    "\n",
    "### 消费者\n",
    "\n",
    "接收消息并且传递到消费者协程中。\n",
    "\n",
    "```py\n",
    "async def consumer(message):\n",
    "    # 模拟处理延迟\n",
    "    await asyncio.sleep(1)\n",
    "    print('consumer: ', message)\n",
    "    \n",
    "async def consumer_handler(websocket, path):\n",
    "    async for message in websocket:\n",
    "        await consumer(message)\n",
    "```\n",
    "\n",
    "**当客户端断开连接后终止迭代**。\n",
    "\n",
    "### 生产者\n",
    "\n",
    "从生产者协程生成消息，并且发送出去\n",
    "\n",
    "```py\n",
    "count = 0\n",
    "async def producer():\n",
    "    global count\n",
    "    # 模拟消息产生延迟\n",
    "    await asyncio.sleep(3)\n",
    "    count += 1\n",
    "    return 'message' + str(count)\n",
    "    \n",
    "async def producer_handler(websocket, path):\n",
    "    while True:\n",
    "        message = await producer()\n",
    "        await websocket.send(message)\n",
    "```        \n",
    "此处生产者代表你的产生消息的业务逻辑。\n",
    "\n",
    "**注意：**当客户端断开连接后，send() 会引发 [`ConnectionClosed`](https://websockets.readthedocs.io/en/stable/api.html#websockets.exceptions.ConnectionClosed)异常，从而从 `while True`的 loop 中 退出。\n",
    "\n",
    "### 生产者+消费者\n",
    "\n",
    "你可以将上面两种模式结合起来，生产者、消费者的任务独立并行，不必互相等待，非常适合开发场景。\n",
    "\n",
    "```py\n",
    "async def handler(websocket, path):\n",
    "    consumer_task = asyncio.ensure_future(\n",
    "        consumer_handler(websocket, path))\n",
    "    producer_task = asyncio.ensure_future(\n",
    "        producer_handler(websocket, path))\n",
    "        # 直到其中一方退出\n",
    "    done, pending = await asyncio.wait(\n",
    "        [consumer_task, producer_task],\n",
    "        return_when=asyncio.FIRST_COMPLETED,\n",
    "    )\n",
    "    for task in pending:\n",
    "        task.cancel()\n",
    "```\n",
    "\n",
    "### 注册客户端\n",
    "\n",
    "参考上面多客户端同步的代码，你需要记录所有已连接的客户端，当他们连接到server时进行注册，断开时注销。\n",
    "\n",
    "```py\n",
    "connected = set()\n",
    "\n",
    "async def handler(websocket, path):\n",
    "    # Register.\n",
    "    connected.add(websocket)\n",
    "    try:\n",
    "        # Implement logic here.\n",
    "        await asyncio.wait([ws.send(\"Hello!\") for ws in connected])\n",
    "        await asyncio.sleep(10)\n",
    "    finally:\n",
    "        # Unregister.\n",
    "        connected.remove(websocket)\n",
    "```        \n",
    "这个简单的示例展示了如何在内存中跟踪连接的客户端，这只在运行单个进程时有效。在实际应用程序中，handler可以注册到消息代理broker 上的某些channels。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cheat sheet\n",
    "\n",
    "## Server\n",
    "\n",
    "- handler处理器: 实现一个协程处理单个连接，接收两个参数（WebSocket协议实例和url 路径参数）\n",
    "\n",
    "    - 随时调用`recv()`和`send()`来接收和发送消息。\n",
    "    - 当`recv()`或`send()`引发`ConnectionClosed`时，清除并退出。如果您启动了其他异步任务，在退出之前终止它们。\n",
    "    - 如果您不是在awaiting `recv()`，可以考虑awaiting [`wait_closed()`](https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol.wait_closed)，以便在连接关闭时快速检测。\n",
    "    - 如果你愿意，你可以[`ping()`](https://websockets.readthedocs.io/en/stable/api.html#websockets.protocol.WebSocketCommonProtocol.ping)或`pong()`，但一般不需要\n",
    "    \n",
    "- 使用`serve()`创建一个服务器，它类似于`asyncio`中loop的`create_server()`。您还可以将它用作异步上下文管理器。\n",
    "\n",
    "    - 服务器负责建立连接，然后让处理程序执行应用程序逻辑，最后在处理程序正常或异常退出后关闭连接\n",
    "    - 对于高级定制，您可以继承`WebSocketServerProtocol`的子类，并将这个子类或工厂函数作为 `create_protocol` 参数传递。\n",
    "    \n",
    "## Client\n",
    "\n",
    "- 使用[`connect()`](https://websockets.readthedocs.io/en/stable/api.html#websockets.client.connect)创建一个客户端，它类似于`asyncio`中loop的`create_connection()`。您还可以将它用作异步上下文管理器。\n",
    "\n",
    "    - 对于高级定制，您可以继承`WebSocketClientProtocol`的子类，并将这个子类或工厂函数作为 `create_protocol` 参数传递。\n",
    "    \n",
    "- 随时调用recv()和send()来接收和发送消息。\n",
    "- 如果你愿意，你可以ping()或pong()，但一般不需要。\n",
    "- 如果没有使用`connect()`作为上下文管理器，请调用`close()`来终止连接。\n",
    "\n",
    "## Debugging\n",
    "\n",
    "如果你不了解websocket的工作原理，请打开日志调试\n",
    "\n",
    "```py\n",
    "import logging\n",
    "logger = logging.getLogger('websockets')\n",
    "logger.setLevel(logging.INFO)\n",
    "logger.addHandler(logging.StreamHandler())\n",
    "```\n",
    "\n",
    "如果你不了解`asyncio`库， 建议查看官方文档[develop with asyncio.](https://docs.python.org/3/library/asyncio-dev.html)\n",
    "\n",
    "## 传递额外的参数到handler\n",
    "\n",
    "```py\n",
    "import asyncio\n",
    "import functools\n",
    "import websockets\n",
    "\n",
    "async def handler(websocket, path, extra_argument):\n",
    "    ...\n",
    "\n",
    "bound_handler = functools.partial(handler, extra_argument='spam')\n",
    "start_server = websockets.serve(bound_handler, '127.0.0.1', 8765)\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```\n",
    "\n",
    "实现此结果的另一种方法是在存在`extra_argument`变量的范围内定义`handler`协程，而不是通过参数注入它。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deployment\n",
    "\n",
    "## 如何关闭server\n",
    "\n",
    "- 使用异步上下文管理器 async with\n",
    "- 调用 `close()` 方法，然后等待它自身的`wait_closed()`方法执行结束。\n",
    "\n",
    "在**Unix系统**上（windows就不要试了），退出通常是通过发送一个信号来触发的。\n",
    "\n",
    "```py\n",
    "import asyncio\n",
    "import signal\n",
    "import websockets\n",
    "\n",
    "async def echo(websocket, path):\n",
    "    async for message in websocket:\n",
    "        await websocket.send(message)\n",
    "\n",
    "async def echo_server(stop):\n",
    "    async with websockets.serve(echo, \"localhost\", 8765):\n",
    "        await stop\n",
    "\n",
    "loop = asyncio.get_event_loop()\n",
    "\n",
    "# The stop condition is set when receiving SIGTERM.\n",
    "stop = loop.create_future()\n",
    "loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)\n",
    "\n",
    "# Run the server until the stop condition is met.\n",
    "loop.run_until_complete(echo_server(stop))\n",
    "```\n",
    "\n",
    "如果你的server不是运行在主线程上，可以使用` call_soon_threadsafe()`.\n",
    "\n",
    "## 端口共享\n",
    "\n",
    "Websocket是HTTP/1.1.的扩展，在同一个端口上同时提供HTTP和WebSocket是ok的。\n",
    "\n",
    "WebSocket的作者并不认为这是一个好主意，因为HTTP和WebSocket的操作特性有很大的不同。\n",
    "\n",
    "`websockets`使用`process_request`参数钩子，为响应HTTP请求提供了最低限度的支持。典型的用例包括健康检查。这里有一个例子\n",
    "\n",
    "```py\n",
    "# WS echo server with HTTP endpoint at /health/\n",
    "\n",
    "import asyncio\n",
    "import http\n",
    "import websockets\n",
    "\n",
    "async def health_check(path, request_headers):\n",
    "    if path == \"/health/\":\n",
    "        return http.HTTPStatus.OK, [], b\"OK\\n\"\n",
    "\n",
    "async def echo(websocket, path):\n",
    "    async for message in websocket:\n",
    "        await websocket.send(message)\n",
    "\n",
    "start_server = websockets.serve(\n",
    "    echo, \"localhost\", 8765, process_request=health_check\n",
    ")\n",
    "\n",
    "asyncio.get_event_loop().run_until_complete(start_server)\n",
    "asyncio.get_event_loop().run_forever()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Extensions\n",
    "\n",
    "websockets 支持扩展\n",
    "\n",
    "[RFC 7692](https://tools.ietf.org/html/rfc7692.html)\n",
    "\n",
    "## WebSocket Per-Message Deflate\n",
    "\n",
    "`serve()` and `connect()` 默认支持Per-Message Deflate， 你可以\n",
    "通过参数禁用`compression=None`.\n",
    "\n",
    "如果希望自定义Per-Message Deflate参数，还可以显式配置每个消息的Deflate扩展。\n",
    "\n",
    "\n",
    "- 服务端\n",
    "\n",
    "```py\n",
    "import websockets\n",
    "from websockets.extensions import permessage_deflate\n",
    "\n",
    "websockets.serve(\n",
    "    ...,\n",
    "    extensions=[\n",
    "        permessage_deflate.ServerPerMessageDeflateFactory(\n",
    "            server_max_window_bits=11,\n",
    "            client_max_window_bits=11,\n",
    "            compress_settings={'memLevel': 4},\n",
    "        ),\n",
    "    ],\n",
    ")\n",
    "```\n",
    "\n",
    "- 客户端\n",
    "\n",
    "```py\n",
    "from websockets.extensions import permessage_deflate\n",
    "\n",
    "websockets.connect(\n",
    "    ...,\n",
    "    extensions=[\n",
    "        permessage_deflate.ClientPerMessageDeflateFactory(\n",
    "            server_max_window_bits=11,\n",
    "            client_max_window_bits=11,\n",
    "            compress_settings={'memLevel': 4},\n",
    "        ),\n",
    "    ],\n",
    ")\n",
    "```\n",
    "\n",
    "参考API文档[ServerPerMessageDeflateFactory](https://websockets.readthedocs.io/en/stable/api.html#websockets.extensions.permessage_deflate.ServerPerMessageDeflateFactory) 和 [ClientPerMessageDeflateFactory](https://websockets.readthedocs.io/en/stable/api.html#websockets.extensions.permessage_deflate.ClientPerMessageDeflateFactory) 了解更多细节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [API](https://websockets.readthedocs.io/en/stable/api.html)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "190.6px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
